package ch.ethz.fcl.metrobuzz.render.od.map_view;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.vecmath.Vector2d;

import ch.ethz.fcl.metrobuzz.MBContainer;
import ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport.PathPublic;
import ch.ethz.fcl.metrobuzz.controller.external.ActionEvent;
import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Edge;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.data.transit.Stop;
import ch.ethz.fcl.metrobuzz.render.od.flow_view.sankey.ODSankeyDiagramData;
import ch.ethz.fcl.metrobuzz.render.techniques.arrow.Arrow;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Polygon;
import ch.ethz.fcl.metrobuzz.spatialindex.object3d.Point3D;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

public class LinkSelection {
	private MBContainer container;

	private MapViewer lttRender;

	int originNodeId = -1;
	int destinationNodeId = -1;

	// int updateSelectedId = -1;

	private float nodeClickRadius = 0;

	private Node curSelectedNode = null;
	private List<Node> selectNodeList;

	private Node curOrigin, curDestination;
	private Node preOrigin, preDestination;

	Edge selectedEdge = null;

	private boolean dragOrigin; // indicate whether origin is dragged
	private boolean dragDestination; // indicate whether destination is dragged

	private boolean clickOnPolygon = false;

	public LinkSelection(MBContainer container, MapViewer render) {
		this.container = container;
		this.lttRender = render;

		nodeClickRadius = container.getData().getContainer().getViewer()
				.getProjection().getWorldDistance(10);
	}

	void handleActionEvent() {
		ActionEvent action = container.getControl().getTuioListener()
				.getTuioEvent();
		if (action == null)
			action = container.getControl().getMouseListener().getLeftMouse();

		if (action != null && !MBData.TIME_CHANGED) {
			Point3D event = null;

			Point2D event2D = null;

			int selectedNodeId = -1;
			if (action.getAction() == ActionEvent.ACTION_DOWN
					|| action.getAction() == ActionEvent.ACTION_DRAG) {
				event = container
						.getViewer()
						.getProjection()
						.getWorldCoord(
								new Point2D(action.getEventX(),
										action.getEventY()), 0);
				event2D = new Point2D(event.getX(), event.getY());
				// selectedNodeId = selectNode(event2D,
				// trajGraph.getMRTStations());
				selectedNodeId = selectNode(event2D, container.getData()
						.getScene().getMRTNodes());
			}

			if (action.getAction() == ActionEvent.ACTION_DOWN) {
				selectedEdge = selectDirection(event2D);
				dragOrigin = false;
				dragDestination = false;

				if (!clickOnPolygon && selectedNodeId != -1) {
					// update selected node id
					if (originNodeId != -1 && originNodeId == selectedNodeId) {
						dragOrigin = true;
						if (curOrigin != null
								&& originNodeId == destinationNodeId) {
							curDestination = container.getData().getScene()
									.getNode(destinationNodeId);

						}
					} else if (destinationNodeId != -1
							&& destinationNodeId == selectedNodeId) {
						dragDestination = true;
					}

					// selected new node id
					else if (originNodeId == -1 || destinationNodeId != -1) {
						originNodeId = selectedNodeId;
						destinationNodeId = -1;

						dragOrigin = true;
						curOrigin = container.getData().getScene()
								.getNode(originNodeId);
						curDestination = null;
					} else {
						destinationNodeId = selectedNodeId;

						curDestination = container.getData().getScene()
								.getNode(destinationNodeId);
					}

					if (originNodeId != -1 && destinationNodeId == -1) {
						curSelectedNode = null;
						selectNodeList = new ArrayList<Node>();
					}

				}
			}

			else if (!clickOnPolygon
					&& action.getAction() == ActionEvent.ACTION_DRAG) {
				if (dragOrigin || dragDestination) {
					container.getControl().updateMouseEvent = false;

					if (selectedNodeId != -1
							&& (curSelectedNode == null || selectedNodeId != container
									.getData().getScene()
									.getNodeIndex(curSelectedNode.getId()))) {
						if (originNodeId != -1 && destinationNodeId == -1) {
							updateNodeList(selectedNodeId);
						} else if (dragOrigin && selectedNodeId != originNodeId) {
							originNodeId = selectedNodeId;
							curOrigin = container.getData().getScene()
									.getNode(originNodeId);
						} else if (dragDestination
								&& selectedNodeId != destinationNodeId) {
							destinationNodeId = selectedNodeId;
							curDestination = container.getData().getScene()
									.getNode(destinationNodeId);
						}
					}

					updateEdgePolygonMap();
				}
			}

			else if (action.getAction() == ActionEvent.ACTION_UP) {
				container.getControl().updateMouseEvent = true;

				if (originNodeId != -1 && destinationNodeId == -1
						&& selectNodeList != null && selectNodeList.size() > 1) {
					curOrigin = container.getData().getScene()
							.getNode(originNodeId);
					curDestination = selectNodeList
							.get(selectNodeList.size() - 1);
					destinationNodeId = container.getData().getScene()
							.getNodeIndex(curDestination.getId());
				}
				dragOrigin = false;
				dragDestination = false;
				clickOnPolygon = false;
				curSelectedNode = null;
			}
		}

		updateQuery();
	}

	private void updateQuery() {
		if ((curOrigin != null)
				&& (curOrigin != preOrigin || curDestination != preDestination || MBData.TIME_CHANGED)) {
			List<Node> nodes = null;
			if (selectNodeList != null && selectNodeList.size() > 2)
				nodes = selectNodeList;
			else {
				nodes = new ArrayList<Node>();
				nodes.add(curOrigin);
				nodes.add(curDestination);
			}
			lttRender.selectLinkQuery.update(nodes, MBData.startTime,
					MBData.period);

			preOrigin = curOrigin;
			preDestination = curDestination;

			if (MBData.TIME_CHANGED) {
				ODSankeyDiagramData.time_changed = true;

				MBData.TIME_CHANGED = false;
			}

			MapViewer.count = 0;
			lttRender.odSankey.update();
		}
	}

	private Edge selectDirection(Point2D event2D) {
		Edge newSelectedEdge = selectedEdge;

		if (originNodeId != -1 && destinationNodeId == originNodeId) {
			for (Edge e : lttRender.edgePolygonMap.keySet()) {
				Polygon p = lttRender.edgePolygonMap.get(e);
				if (p.contains(event2D)) {
					curOrigin = e.getStartNode();
					curDestination = e.getEndNode();
					if (curOrigin == null || curDestination == null
							|| preOrigin == null || preDestination == null
							|| curOrigin != preOrigin
							|| curDestination != preDestination) {
						List<Node> nodes = new ArrayList<Node>();
						nodes.add(curOrigin);
						nodes.add(curDestination);

						lttRender.selectLinkQuery.update(nodes,
								MBData.startTime, MBData.period);

						lttRender.odSankey.update();

						preOrigin = curOrigin;
						preDestination = curDestination;

						clickOnPolygon = true;
						newSelectedEdge = e;
					}

					break;
				}
			}
		}

		return newSelectedEdge;
	}

	private void updateEdgePolygonMap() {
		if (originNodeId != -1 && destinationNodeId == originNodeId
				&& (curOrigin != preOrigin || curDestination != preDestination)) {
			Node node = container.getData().getScene().getNode(originNodeId);
			// selectedEdge = null;
			lttRender.edgePolygonMap = new HashMap<Edge, Polygon>();

			List<Edge> outEdges = node.getOutEdges();
			float headWidth = 0.02f;
			for (Edge e : outEdges) {
				Vector2d dir = node.toPoint().getDir(e.getEndNode().toPoint());

				Point2D head = node.toPoint().getPointAt(dir, headWidth * 2f);
				Polygon polygon = Arrow.generateDefaultArrow(head, dir,
						headWidth);

				lttRender.edgePolygonMap.put(e, polygon);
			}

			if (dragOrigin) {
				Stop startStop = container.getData().getPublicTransit()
						.getStopByNodeId(preOrigin.getId());
				Stop endStop = container.getData().getPublicTransit()
						.getStopByNodeId(preDestination.getId());
				PathPublic p = container.getData().getAllToAllDijkstra()
						.getPath(startStop, endStop);
				List<Node> nodes = p.getAllNodes();
				selectedEdge = container
						.getData()
						.getScene()
						.getEdge(nodes.get(nodes.size() - 2),
								nodes.get(nodes.size() - 1));
			}
			if (dragDestination) {
				Stop startStop = container.getData().getPublicTransit()
						.getStopByNodeId(preDestination.getId());
				Stop endStop = container.getData().getPublicTransit()
						.getStopByNodeId(preOrigin.getId());
				PathPublic p = container.getData().getAllToAllDijkstra()
						.getPath(startStop, endStop);
				List<Node> nodes = p.getAllNodes();
				Edge edge = container
						.getData()
						.getScene()
						.getEdge(nodes.get(nodes.size() - 2),
								nodes.get(nodes.size() - 1));

				double angle = Double.MAX_VALUE;
				for (Edge e : outEdges) {
					double a = Math
							.abs(MBTools.angle(edge.getDir(), e.getDir()));
					if (a < angle) {
						angle = a;
						selectedEdge = e;
					}
				}
			}
			curOrigin = selectedEdge.getStartNode();
			curDestination = selectedEdge.getEndNode();

			List<Node> nodes = new ArrayList<Node>();
			nodes.add(curOrigin);
			nodes.add(curDestination);

			lttRender.selectLinkQuery.update(nodes, MBData.startTime,
					MBData.period);
			lttRender.odSankey.update();

			preOrigin = curOrigin;
			preDestination = curDestination;
		}
	}

	private void updateNodeList(int nodeId) {
		Node newNode = container.getData().getScene().getNode(nodeId);

		if (curSelectedNode == null) {
			curSelectedNode = newNode;
			selectNodeList.add(newNode);
		} else
			for (Edge e : curSelectedNode.getOutEdges()) {
				Node endNode = e.getEndNode();
				if (endNode.equals(newNode)
						&& !selectNodeList.contains(newNode)) {
					curSelectedNode = newNode;
					selectNodeList.add(newNode);
					break;
				}
			}
	}

	private int selectNode(Point2D point, List<Node> nodeList) {
		float minDist = Float.MAX_VALUE;

		int selectedNodeId = -1;
		for (Node node : nodeList) {
			float dist = node.toPoint().distance(point);
			if (dist < nodeClickRadius && dist < minDist) {
				selectedNodeId = container.getData().getScene()
						.getNodeIndex(node.getId());

				minDist = dist;
			}
		}

		return selectedNodeId;
	}
}
